/**
 * Copyright (c) 2014- 2015 YCY Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.apache.shiro.realm;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import com.alibaba.fastjson.JSON;
import com.jkwzms.common.repository.support.SimpleBaseRepositoryFactoryBean;
import com.jkwzms.system.auth.service.UserAuthService;
import com.jkwzms.system.user.entity.User;
import com.jkwzms.system.user.exception.UserBlockedException;
import com.jkwzms.system.user.exception.UserException;
import com.jkwzms.system.user.exception.UserNotExistsException;
import com.jkwzms.system.user.exception.UserPasswordNotMatchException;
import com.jkwzms.system.user.exception.UserPasswordRetryLimitExceedException;
import com.jkwzms.system.user.service.UserService;

/**
 * <p>
 * User: Jack Liang
 * <p>
 * Date: 13-3-12 下午9:05
 * <p>
 * Version:1.0.0
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService         userService;
    @Autowired
    private UserAuthService     userAuthService;

    private static final Logger log = LoggerFactory.getLogger("jkwzms-error");

    @Autowired
    public UserRealm(ApplicationContext ctx){
        super();
        // 不能注入 因为获取bean依赖顺序问题造成可能拿不到某些bean报错
        // why？
        // 因为spring在查找findAutowireCandidates时对FactoryBean做了优化，即只获取Bean，但不会autowire属性，
        // 所以如果我们的bean在依赖它的bean之前初始化，那么就得不到ObjectType（永远是Repository）
        // 所以此处我们先getBean一下 就没有问题了
        ctx.getBeansOfType(SimpleBaseRepositoryFactoryBean.class);
    }

    /**
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Principal principal = (Principal) principals.getPrimaryPrincipal();

        User user = userService.findByUsername(principal.getLoginName());

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(userAuthService.findStringRoles(user));
        authorizationInfo.setStringPermissions(userAuthService.findStringPermissions(user));

        return authorizationInfo;
    }

    private static final String OR_OPERATOR  = " or ";
    private static final String AND_OPERATOR = " and ";
    private static final String NOT_OPERATOR = " not ";

    /**
     * 支持or and not 关键词 不支持and or混用
     * 
     * @param principals
     * @param permission
     * @return
     */
    public boolean isPermitted(PrincipalCollection principals, String permission) {
        if (permission.contains(OR_OPERATOR)) {
            String[] permissions = permission.split(OR_OPERATOR);
            for (String orPermission : permissions) {
                if (isPermittedWithNotOperator(principals, orPermission)) {
                    return true;
                }
            }
            return false;
        } else if (permission.contains(AND_OPERATOR)) {
            String[] permissions = permission.split(AND_OPERATOR);
            for (String orPermission : permissions) {
                if (!isPermittedWithNotOperator(principals, orPermission)) {
                    return false;
                }
            }
            return true;
        } else {
            return isPermittedWithNotOperator(principals, permission);
        }
    }

    private boolean isPermittedWithNotOperator(PrincipalCollection principals, String permission) {
        if (permission.startsWith(NOT_OPERATOR)) {
            return !super.isPermitted(principals, permission.substring(NOT_OPERATOR.length()));
        } else {
            return super.isPermitted(principals, permission);
        }
    }

    /**
     * 认证回调函数, 登录时调用
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername().trim();
        String password = "";
        if (upToken.getPassword() != null) {
            password = new String(upToken.getPassword());
        }

        User user = null;
        try {
            user = userService.login(username, password);
        } catch (UserNotExistsException e) {
            throw new UnknownAccountException(e.getMessage(), e);
        } catch (UserPasswordNotMatchException e) {
            throw new AuthenticationException(e.getMessage(), e);
        } catch (UserPasswordRetryLimitExceedException e) {
            throw new ExcessiveAttemptsException(e.getMessage(), e);
        } catch (UserBlockedException e) {
            throw new LockedAccountException(e.getMessage(), e);
        } catch (Exception e) {
            log.error("login error!", e);
            throw new AuthenticationException(new UserException("user.unknown.error", null));
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(new Principal(user), password.toCharArray(),
                                                                     getName());
        return info;
    }

    /**
     * @author JackLiang
     * @date 2015-7-28
     * @version 1.0.0
     */
    public static class Principal implements Serializable {

        private static final long   serialVersionUID = 3331378252561182510L;

        private Long                id;
        // 登录ID
        private String              loginName;
        // 用户真实姓名
        private String              name;
        // 缓存信息
        private Map<String, Object> cacheMap;

        public Principal(User user){
            this.id = user.getId();
            this.loginName = user.getUsername();
            this.name = user.getFullName();
        }

        public Principal(){
        }

        public Long getId() {
            return id;
        }

        public String getLoginName() {
            return loginName;
        }

        public String getName() {
            return name;
        }

        public Map<String, Object> getCacheMap() {
            if (cacheMap == null) {
                cacheMap = new HashMap<String, Object>();
            }
            return cacheMap;
        }

    }

}
